/*
 * Copyright 2022 The Open Islands Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openislands.oi.console.utils;


import com.google.common.base.CaseFormat;
import org.openislands.oi.console.config.bean.LoadBeanHandler;
import org.openislands.oi.console.exception.InvokeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

public class ParameterUtils implements LoadBeanHandler {
    private static final Logger log = LoggerFactory.getLogger(ParameterUtils.class);

    /***
     * Load see {@link #initBean(Map)}
     */
    private static final Map<Class<?>, Cast<?>> typeCastCache = new HashMap<>();
    private static final List<MatchCast<?>> typeMatchCastCache = new ArrayList<>();

    /***
     * See {@link Cast#cast(String)} {@link #typeCastCache}
     * @param eClass Cast Type class
     * @param value  value
     * @param <E>    Cast Type
     * @return cast value
     */
    @SuppressWarnings("unchecked")
    public static <E> E valueOf(Class<E> eClass, Object value) {
        Cast<?> cast = typeCastCache.get(eClass);
        return value == null ? null : cast == null ? valueOfMatch(eClass, value) : ((E) cast.cast(value.toString()));
    }

    @SuppressWarnings("unchecked")
    public static <E> E valueOfMatch(Class<E> eClass, Object value) {
        for (MatchCast<?> matchCast : typeMatchCastCache) {
            if (matchCast.match(eClass)) {
                return ((E) matchCast.cast(eClass, value.toString()));
            }
        }
        return null;
    }

    public static String smallHumpTurnEnDash(String str) {
        return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, str);
    }

    public static <T> T getFirst(List<T> objects) {
        return CollectionUtils.isEmpty(objects) ? null : objects.get(0);
    }

    public static String[] filterParams(String[] args, String... filterNames) {
        if (filterNames == null || filterNames.length == 0) return args;
        Set<String> filter = Arrays.stream(filterNames).collect(Collectors.toSet());

        List<String> result = new ArrayList<>(args.length);
        for (int i = 0; i < args.length; i++) {
            if (filter.contains(args[i])) {
                i++;
                continue;
            }
            result.add(args[i]);
        }
        return result.toArray(new String[0]);
    }

    /***
     * Load class implements of {@link Cast}
     * @param classes   pending class set
     * @param resultMap bean container
     */
    @Override
    public void loadBean(Set<Class<?>> classes, Map<Class<?>, List<Object>> resultMap) {
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    @Override
    public void initBean(Map<Class<?>, List<Object>> beanMap) {
        for (Class<?> aClass : beanMap.keySet()) {
            if (Cast.class.isAssignableFrom(aClass)) {
                typeCastCache.put(aClass, (Cast<?>) beanMap.get(aClass).get(0));
                log.debug("add type cast: " + aClass);
            } else if (MatchCast.class.isAssignableFrom(aClass)) {
                typeMatchCastCache.addAll(((List) beanMap.get(aClass)));
                log.debug("add type match cast: " + aClass);
            }
        }
    }

    /***
     * Specify type converter
     * @param <E> specify type
     */
    public interface Cast<E> {
        E cast(String obj);
    }

    /***
     * Match type converter
     * @param <E> match type
     */
    public interface MatchCast<E> {
        boolean match(Class<?> matchClass);

        E cast(Class<?> matchClass, String obj);
    }

    @Component
    @SuppressWarnings("all")
    public static class EnumMatchCast implements MatchCast<Enum<?>> {
        @Override
        public Enum<?> cast(Class<?> matchClass, String obj) {
            try {
                return Enum.valueOf((Class) matchClass, obj);
            } catch (Exception e) {
                throw new InvokeException(matchClass.getSimpleName() + " no " + obj
                        + " filed, optional is " + Arrays.toString(matchClass.getEnumConstants()));
            }
        }

        @Override
        public boolean match(Class<?> matchClass) {
            return Enum.class.isAssignableFrom(matchClass);
        }
    }

    static {
        typeCastCache.put(Byte.class, Byte::valueOf);
        typeCastCache.put(Boolean.class, Boolean::valueOf);
        typeCastCache.put(Character.class, s -> s.charAt(0));
        typeCastCache.put(Short.class, Short::valueOf);
        typeCastCache.put(Integer.class, Integer::valueOf);
        typeCastCache.put(Float.class, Float::valueOf);
        typeCastCache.put(Long.class, Long::valueOf);
        typeCastCache.put(Double.class, Double::valueOf);
        typeCastCache.put(String.class, s -> s);
    }
}
